package com.ruoyi.file.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.file.domain.SysStorageConfig;
import com.ruoyi.file.domain.dto.*;
import com.ruoyi.file.domain.vo.BatchPresignedUrlVO;
import com.ruoyi.file.domain.vo.InitMultipartVO;
import com.ruoyi.file.domain.vo.PresignedUrlVO;
import com.ruoyi.file.domain.vo.UploadTokenVO;
import com.ruoyi.file.service.ISysStorageConfigService;
import com.ruoyi.file.service.storage.StorageService;
import com.ruoyi.file.service.storage.StorageServiceFactory;
import com.ruoyi.file.utils.FilePathUtils;
import com.ruoyi.file.utils.FileValidationUtils;
import com.ruoyi.file.utils.ResponseUtils;
import com.ruoyi.file.utils.StorageConfigUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * 文件上传控制器
 *
 * @author ruoyi
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/file")
@Tag(name = "文件上传管理", description = "文件上传相关接口")
public class FileUploadController {

    private final ISysStorageConfigService storageConfigService;
    private final StorageServiceFactory storageServiceFactory;
    
    // 分片上传元数据缓存 - 在生产环境中应该使用Redis
    private static final ConcurrentMap<String, MultipartUploadMetadata> multipartCache = new ConcurrentHashMap<>();
    
    // 定时清理过期缓存的调度器
    private ScheduledExecutorService cleanupScheduler;
    
    /**
     * 分片上传元数据
     */
    @Getter
    private static class MultipartUploadMetadata {
        private final String filePath;
        private final Long configId;
        private final String storageType;
        private final long timestamp;
        
        public MultipartUploadMetadata(String filePath, Long configId, String storageType) {
            this.filePath = filePath;
            this.configId = configId;
            this.storageType = storageType;
            this.timestamp = System.currentTimeMillis();
        }

        public boolean isExpired() {
            // 24小时过期
            return System.currentTimeMillis() - timestamp > 24 * 60 * 60 * 1000L;
        }
    }
    
    @PostConstruct
    public void init() {
        // 启动定时清理任务，每小时执行一次
        cleanupScheduler = Executors.newSingleThreadScheduledExecutor();
        cleanupScheduler.scheduleAtFixedRate(this::cleanupExpiredCache, 1, 1, TimeUnit.HOURS);
    }
    
    @PreDestroy
    public void destroy() {
        if (cleanupScheduler != null) {
            cleanupScheduler.shutdown();
        }
    }
    
    /**
     * 清理过期的分片上传缓存
     */
    private void cleanupExpiredCache() {
        try {
            multipartCache.entrySet().removeIf(entry -> entry.getValue().isExpired());
        } catch (Exception e) {
            log.warn("清理过期分片上传缓存失败: {}", e.getMessage());
        }
    }

    /**
     * 获取上传令牌
     */
    @Operation(summary = "获取上传令牌")
    @SaCheckPermission("file:upload:token")
    @PostMapping("/token")
    public AjaxResult getUploadToken(@Validated @RequestBody UploadTokenRequest request) {
        try {
            // 获取并验证存储配置
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(request.getConfigId(), storageConfigService);

            // 验证文件类型
            String fileTypeError = FileValidationUtils.getFileTypeValidationError(request.getFileName(), config.getAllowedFileTypes());
            if (fileTypeError != null) {
                return AjaxResult.error(fileTypeError);
            }

            // 生成最终的文件名（支持自定义保存文件名）
            String finalFileName;
            try {
                finalFileName = FilePathUtils.generateFinalFileName(request.getFileName(), request.getSaveFileName());
            } catch (IllegalArgumentException e) {
                return AjaxResult.error(e.getMessage());
            }

            // 获取对应的存储服务并生成上传令牌
            StorageService storageService = storageServiceFactory.getStorageService(config);
            UploadTokenVO tokenVO = storageService.generateUploadToken(finalFileName, config, request.getDirPath());

            return AjaxResult.success("生成上传令牌成功", tokenVO);
        } catch (Exception e) {
            log.error("生成上传令牌失败: {}", e.getMessage(), e);
            return AjaxResult.error("生成上传令牌失败: " + e.getMessage());
        }
    }

    /**
     * 上传回调
     */
    @Operation(summary = "上传回调")
    @PostMapping("/callback")
    public AjaxResult uploadCallback(@Validated @RequestBody UploadCallbackRequest request) {
        try {
            // 验证存储配置
            SysStorageConfig config = storageConfigService.getConfigById(request.getConfigId());
            if (config == null) {
                return AjaxResult.error("存储配置不存在");
            }

            // 从请求中获取文件路径（相对路径）
            String filePath = request.getFilePath();
            if (StringUtils.isBlank(filePath)) {
                // 如果请求中没有filePath，尝试从fileUrl中解析
                filePath = request.getFileUrl();
            }

            // 获取存储服务并生成可访问的URL
            StorageService storageService = storageServiceFactory.getStorageService(config);
            String fileUrl = storageService.getFileUrl(filePath, config);

            // 构建统一的响应对象
            UploadTokenVO response = ResponseUtils.buildUploadSuccessResponse(filePath, request.getFileName(), request.getFileSize(), fileUrl, config);

            return ResponseUtils.success("上传回调处理成功", response);
        } catch (Exception e) {
            log.error("上传回调处理失败: {}", e.getMessage(), e);
            return AjaxResult.error("上传回调处理失败: " + e.getMessage());
        }
    }

    /**
     * 文件上传
     */
    @Operation(summary = "文件上传")
    @SaCheckPermission("file:upload:upload")
    @Log(title = "文件上传", businessType = BusinessType.INSERT)
    @PostMapping("/upload")
    public AjaxResult upload(@RequestParam("file") MultipartFile file, 
                           @RequestParam(value = "configId", required = false) Long configId, 
                           @RequestParam(value = "dirPath", required = false) String dirPath,
                           @RequestParam(value = "saveFileName", required = false) String saveFileName) {
        try {
            if (file.isEmpty()) {
                return AjaxResult.error("上传文件不能为空");
            }

            // 获取存储配置
            SysStorageConfig config;
            if (configId != null) {
                config = storageConfigService.getConfigById(configId);
            } else {
                config = storageConfigService.getDefaultConfig();
            }
            if (config == null) {
                return AjaxResult.error("存储配置不存在");
            }

            // 检查配置状态
            if (config.getStatus() == 0) {
                return AjaxResult.error("存储配置已停用");
            }

            // 验证文件大小
            if (file.getSize() > config.getMaxFileSize()) {
                return AjaxResult.error("文件大小超过限制");
            }

            // 验证文件类型
            String fileName = file.getOriginalFilename();
            String fileTypeError = FileValidationUtils.getFileTypeValidationError(fileName, config.getAllowedFileTypes());
            if (fileTypeError != null) {
                return ResponseUtils.error(fileTypeError);
            }

            // 生成最终的文件名（支持自定义保存文件名）
            String finalFileName;
            try {
                finalFileName = FilePathUtils.generateFinalFileName(fileName, saveFileName);
            } catch (IllegalArgumentException e) {
                return AjaxResult.error(e.getMessage());
            }

            // 使用存储服务上传文件
            StorageService storageService = storageServiceFactory.getStorageService(config);
            String filePath;
            try (InputStream inputStream = file.getInputStream()) {
                filePath = storageService.uploadFile(inputStream, finalFileName, file.getContentType(), config, dirPath);
            }

            // 获取可访问的URL
            String fileUrl = storageService.getFileUrl(filePath, config);

            // 构建统一的响应对象
            UploadTokenVO response = ResponseUtils.buildUploadSuccessResponse(filePath, fileName, file.getSize(), fileUrl, config);

            return ResponseUtils.success("文件上传成功", response);
        } catch (Exception e) {
            log.error("文件上传失败: {}", e.getMessage(), e);
            return AjaxResult.error("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 获取文件访问URL
     */
    @Operation(summary = "获取文件访问URL")
    @GetMapping("/url")
    public AjaxResult getFileUrl(@RequestParam("filePath") String filePath, @RequestParam(value = "configId", required = false) Long configId) {
        try {
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(configId, storageConfigService);

            StorageService storageService = storageServiceFactory.getStorageService(config);
            String fileUrl = storageService.getFileUrl(filePath, config);

            AjaxResult ajax = AjaxResult.success("获取文件URL成功");
            ajax.put("url", fileUrl);
            ajax.put("filePath", filePath);
            return ajax;
        } catch (Exception e) {
            log.error("获取文件URL失败: {}", e.getMessage(), e);
            return AjaxResult.error("获取文件URL失败: " + e.getMessage());
        }
    }

    /**
     * 删除文件
     */
    @Operation(summary = "删除文件")
    @SaCheckPermission("file:upload:delete")
    @Log(title = "删除文件", businessType = BusinessType.DELETE)
    @DeleteMapping("/delete")
    public AjaxResult deleteFile(@RequestParam("filePath") String filePath, @RequestParam(value = "configId", required = false) Long configId) {
        try {
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(configId, storageConfigService);

            StorageService storageService = storageServiceFactory.getStorageService(config);
            boolean success = storageService.deleteFile(filePath, config);

            if (success) {
                return AjaxResult.success("文件删除成功");
            } else {
                return AjaxResult.error("文件删除失败");
            }
        } catch (Exception e) {
            log.error("文件删除失败: {}", e.getMessage(), e);
            return AjaxResult.error("文件删除失败: " + e.getMessage());
        }
    }

    /**
     * 分片上传接口
     */
    @Operation(summary = "分片上传")
    @SaCheckPermission("file:upload:chunkUpload")
    @PostMapping("/chunk/upload")
    public AjaxResult chunkUpload(@RequestParam("uploadId") String uploadId, 
                                @RequestParam("partNumber") Integer partNumber, 
                                @RequestParam("file") MultipartFile file,
                                @RequestParam(value = "configId", required = false) Long configId,
                                @RequestParam(value = "filePath", required = false) String filePath) {
        long startTime = System.currentTimeMillis();
        try {
            // 基本参数验证
            if (uploadId == null || uploadId.trim().isEmpty()) {
                return AjaxResult.error(400, "uploadId参数不能为空");
            }
            if (partNumber == null || partNumber <= 0) {
                return AjaxResult.error(400, "partNumber参数必须为正整数");
            }
            if (file == null || file.isEmpty()) {
                return AjaxResult.error(400, "上传文件不能为空");
            }

            // 获取存储配置和文件路径
            SysStorageConfig config;
            String finalFilePath;
            
            if (configId != null && StringUtils.isNotBlank(filePath)) {
                // 参数中有配置信息，直接使用
                config = StorageConfigUtils.getAndValidateConfig(configId, storageConfigService);
                finalFilePath = filePath;
            } else {
                // 从缓存中获取
                MultipartUploadMetadata metadata = multipartCache.get(uploadId);
                if (metadata == null) {
                    throw new ServiceException("分片上传会话已过期，请重新初始化分片上传");
                }
                config = StorageConfigUtils.getAndValidateConfig(metadata.getConfigId(), storageConfigService);
                finalFilePath = metadata.getFilePath();
            }

            // 上传分片
            StorageService storageService = storageServiceFactory.getStorageService(config);
            String realETag;
            
            try (InputStream inputStream = file.getInputStream()) {
                realETag = storageService.uploadPart(finalFilePath, uploadId, partNumber, inputStream, config);
            }
            
            long duration = System.currentTimeMillis() - startTime;
            
            AjaxResult ajax = AjaxResult.success("分片上传成功");
            ajax.put("uploadId", uploadId);
            ajax.put("partNumber", partNumber);
            ajax.put("etag", realETag);
            ajax.put("duration", duration);
            return ajax;
            
        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            log.error("分片上传失败: uploadId={}, partNumber={}, size={}, duration={}ms, error={}",
                    uploadId, partNumber, file != null ? file.getSize() : 0, duration, e.getMessage(), e);
            // 根据异常类型返回不同的错误码
            if (e instanceof java.io.IOException) {
                return AjaxResult.error(503, "磁盘IO错误，请重试: " + e.getMessage());
            } else {
                return AjaxResult.error(500, "分片上传失败，请重试: " + e.getMessage());
            }
        }
    }

    /**
     * 初始化分片上传
     */
    @Operation(summary = "初始化分片上传")
    @SaCheckPermission("file:upload:init")
    @Log(title = "初始化分片上传", businessType = BusinessType.INSERT)
    @PostMapping("/multipart/init")
    public AjaxResult initMultipartUpload(@Validated @RequestBody InitMultipartRequest request) {
        try {
            // 获取并验证存储配置
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(request.getConfigId(), storageConfigService);

            // 验证文件大小
            if (request.getFileSize() != null) {
                String sizeError = FileValidationUtils.getFileSizeValidationError(request.getFileSize(), config.getMaxFileSize());
                if (sizeError != null) {
                    return AjaxResult.error(sizeError);
                }
            }

            // 验证文件类型
            String fileTypeError = FileValidationUtils.getFileTypeValidationError(request.getFileName(), config.getAllowedFileTypes());
            if (fileTypeError != null) {
                return AjaxResult.error(fileTypeError);
            }

            // 生成最终的文件名（支持自定义保存文件名）
            String finalFileName;
            try {
                finalFileName = FilePathUtils.generateFinalFileName(request.getFileName(), request.getSaveFileName());
            } catch (IllegalArgumentException e) {
                return AjaxResult.error(e.getMessage());
            }

            // 生成完整文件路径（只拼接路径，不添加时间戳）
            String filePath = FilePathUtils.combinePathAndFileName(finalFileName, request.getDirPath());

            // 获取对应的存储服务并初始化分片上传
            StorageService storageService = storageServiceFactory.getStorageService(config);
            String uploadId = storageService.initMultipartUpload(filePath, config);

            // 缓存分片上传元数据
            multipartCache.put(uploadId, new MultipartUploadMetadata(filePath, config.getConfigId(), config.getStorageType()));

            InitMultipartVO response = new InitMultipartVO();
            response.setUploadId(uploadId);
            response.setFilePath(filePath);
            response.setConfigId(config.getConfigId());
            response.setBucketName(config.getBucketName());
            response.setMessage("初始化分片上传成功");

            return AjaxResult.success("初始化分片上传成功", response);
        } catch (Exception e) {
            log.error("初始化分片上传失败: {}", e.getMessage(), e);
            return AjaxResult.error("初始化分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 批量获取分片预签名上传URL
     */
    @Operation(summary = "批量获取分片预签名上传URL")
    @SaCheckPermission("file:upload:multipart")
    @PostMapping("/multipart/batch-presign")
    public AjaxResult getBatchPresignedUploadUrls(@Validated @RequestBody BatchPresignedUrlRequest request) {
        long startTime = System.currentTimeMillis();
        try {
            // 参数验证
            if (request.getTotalParts() <= 0 || request.getTotalParts() > 10000) {
                return AjaxResult.error(400, "分片数量必须在1-10000之间");
            }
            if (StringUtils.isBlank(request.getUploadId())) {
                return AjaxResult.error(400, "uploadId参数不能为空");
            }
            if (StringUtils.isBlank(request.getFilePath())) {
                return AjaxResult.error(400, "文件路径不能为空");
            }

            // 获取并验证存储配置
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(request.getConfigId(), storageConfigService);

            // 检查存储服务是否支持直传
            if (!isDirectUploadSupported(config)) {
                return AjaxResult.error(400, "当前存储配置不支持直传功能");
            }

            // 批量生成预签名URL
            StorageService storageService = storageServiceFactory.getStorageService(config);
            List<String> presignedUrls;

            try {
                presignedUrls = storageService.getBatchPresignedUploadUrls(request.getFilePath(), request.getUploadId(), request.getTotalParts(), config);
            } catch (Exception e) {
                log.error("存储服务生成预签名URL失败: uploadId={}, error={}", request.getUploadId(), e.getMessage());
                return AjaxResult.error(503, "存储服务暂时不可用，请稍后重试: " + e.getMessage());
            }

            // 验证返回的URL数量
            if (presignedUrls == null || presignedUrls.size() != request.getTotalParts()) {
                log.error("预签名URL数量不匹配: expected={}, actual={}", request.getTotalParts(),
                        presignedUrls != null ? presignedUrls.size() : 0);
                return AjaxResult.error(500, "预签名URL生成不完整，请重试");
            }

            // 构建响应对象
            List<PresignedUrlVO> urlVOs = new java.util.ArrayList<>();
            for (int i = 0; i < presignedUrls.size(); i++) {
                String url = presignedUrls.get(i);
                if (StringUtils.isBlank(url)) {
                    log.error("第{}个预签名URL为空", i + 1);
                    return AjaxResult.error(500, "预签名URL生成失败，请重试");
                }

                PresignedUrlVO urlVO = new PresignedUrlVO();
                urlVO.setUploadUrl(url);
                urlVO.setPartNumber(i + 1);
                urlVO.setUploadId(request.getUploadId());
                urlVO.setExpiresIn(3600L); // 1小时
                urlVOs.add(urlVO);
            }

            BatchPresignedUrlVO response = new BatchPresignedUrlVO();
            response.setUrls(urlVOs);
            response.setUploadId(request.getUploadId());
            response.setTotalParts(request.getTotalParts());
            response.setExpiresIn(3600L);
            response.setMessage("批量获取预签名URL成功");
            return AjaxResult.success("批量获取预签名URL成功", response);

        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            log.error("批量获取预签名URL失败: uploadId={}, totalParts={}, duration={}ms, error={}",
                    request.getUploadId(), request.getTotalParts(), duration, e.getMessage(), e);

            // 根据异常类型返回不同错误码
            if (e.getMessage().contains("network") || e.getMessage().contains("timeout")) {
                return AjaxResult.error(504, "网络超时，请重试: " + e.getMessage());
            } else if (e.getMessage().contains("permission") || e.getMessage().contains("access")) {
                return AjaxResult.error(403, "存储权限不足: " + e.getMessage());
            } else {
                return AjaxResult.error(500, "批量获取预签名URL失败，请重试: " + e.getMessage());
            }
        }
    }

    /**
     * 检查存储配置是否支持直传
     */
    private boolean isDirectUploadSupported(SysStorageConfig config) {
        if (config == null) return false;

        String storageType = config.getStorageType().toLowerCase();
        boolean typeSupported = "minio".equals(storageType) || "aliyun".equals(storageType)
                || "tencent".equals(storageType) || "aws".equals(storageType);

        boolean directUploadEnabled = config.getEnableDirectUpload() == 1
                || "1".equals(String.valueOf(config.getEnableDirectUpload()));

        return typeSupported && directUploadEnabled;
    }

    /**
     * 完成分片上传
     */
    @Operation(summary = "完成分片上传")
    @SaCheckPermission("file:upload:multipart")
    @Log(title = "完成分片上传", businessType = BusinessType.INSERT)
    @PostMapping("/multipart/complete")
    public AjaxResult completeMultipartUpload(@Validated @RequestBody CompleteMultipartRequest request) {
        try {
            // 获取并验证存储配置
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(request.getConfigId(), storageConfigService);

            // 完成分片上传
            StorageService storageService = storageServiceFactory.getStorageService(config);
            boolean success = storageService.completeMultipartUpload(request.getFilePath(), request.getUploadId(), request.getParts(), config);

            if (success) {
                // 清理缓存
                multipartCache.remove(request.getUploadId());
                
                // 生成访问URL
                String fileUrl = storageService.getFileUrl(request.getFilePath(), config);

                // 构建统一的响应对象
                UploadTokenVO response = ResponseUtils.buildUploadSuccessResponse(request.getFilePath(), null, null, fileUrl, config);

                return ResponseUtils.success("分片上传完成", response);
            } else {
                return AjaxResult.error("完成分片上传失败");
            }
        } catch (Exception e) {
            log.error("完成分片上传失败: {}", e.getMessage(), e);
            return AjaxResult.error("完成分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 取消分片上传
     */
    @Operation(summary = "取消分片上传")
    @SaCheckPermission("file:upload:multipart")
    @DeleteMapping("/multipart/abort")
    public AjaxResult abortMultipartUpload(@RequestParam("filePath") String filePath, @RequestParam("uploadId") String uploadId, @RequestParam(value = "configId", required = false) Long configId) {
        try {
            // 获取存储配置
            SysStorageConfig config = StorageConfigUtils.getAndValidateConfig(configId, storageConfigService);

            // 取消分片上传
            StorageService storageService = storageServiceFactory.getStorageService(config);
            boolean success = storageService.abortMultipartUpload(filePath, uploadId, config);

            if (success) {
                // 清理缓存
                multipartCache.remove(uploadId);
                
                return AjaxResult.success("取消分片上传成功");
            } else {
                return AjaxResult.error("取消分片上传失败");
            }
        } catch (Exception e) {
            log.error("取消分片上传失败: {}", e.getMessage(), e);
            return AjaxResult.error("取消分片上传失败: " + e.getMessage());
        }
    }
}